package me.zhengjie.modules.bailian.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationParam;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.aliyun.bailian20231229.Client;
import com.aliyun.bailian20231229.models.*;
import com.aliyun.teaopenapi.models.Config;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.modules.bailian.domain.EosSystemFunctionDescription;

import java.io.File;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 阿里云百炼工具 2025/3/18 14:32
 *
 * @author Yaco
 * @version 1.0.0
 */
@Slf4j
public class AliyunBailianUtils {

    //    private static final String SDK_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
    private static final String MODEL_TYPE = "qwen-max";
    //    private static final String dashscope_api_key = "sk-7f9ddf1152dc41e7a68559deae53568d";
    private static final String MODEL_ROLE_CONTENT = "你是一个文档处理专家，不要和用户沟通交流。你仅负责把用户发送给你的文档，按照要求改写并返回结果。如果处理成功，只返回改写后的结果；如果处理失败，则返回文字“9999”。文档处理要求如下：请重新整理这段文字，按章节输出；同时，请检查文章的文字是否有错误，若有请修正；并请将文章中的口语话表述修改为专业的描述。请以markdown格式输出。";

    private static String getFileMd5(String filePath) throws Exception {
        log.info(" ### getFileMd5 filePath = {}", filePath);
        MessageDigest digest = MessageDigest.getInstance("MD5");
        try (InputStream is = Files.newInputStream(Paths.get(filePath))) {
            byte[] buffer = new byte[1024];
            int read;
            while ((read = is.read(buffer)) > 0) {
                digest.update(buffer, 0, read);
            }
        }
        byte[] md5Bytes = digest.digest();

        StringBuilder md5String = new StringBuilder();
        for (byte b : md5Bytes) {
            md5String.append(String.format("%02x", b));
        }
        String md5 = md5String.toString();
        log.info(" ### getFileMd5 filePath = {}, md5 = [{}]", filePath, md5);
        return md5;
    }

    /**
     * 写入Excel 2025/3/19 21:06
     *
     * @param fullPath 1
     * @param rows     2
     * @return boolean
     * @author Yaco
     * @since 1.0.0
     */
    public static boolean writeExcel(String fullPath, List<EosSystemFunctionDescription> rows) {
        log.info(" ### AliyunBailianUtils[writeExcel] 写入文件 ==>> {}.", fullPath);
        try {
            ExcelWriter writer = ExcelUtil.getWriter(fullPath);
            writer.addHeaderAlias("tag1", "标签1");
            writer.addHeaderAlias("tag2", "标签2");
            writer.addHeaderAlias("tag3", "标签3");
            writer.addHeaderAlias("knowledgeTopic", "知识主题");
            writer.addHeaderAlias("knowledgeSliceFinal", "知识切片");
            writer.addHeaderAlias("metadataImage", "相关元数据：图片");
            writer.addHeaderAlias("metadataResponseMethod", "相关元数据：响应方式");
            writer.addHeaderAlias("metadataSlotDependency", "相关元数据：槽位依赖");
            writer.addHeaderAlias("metadataToolInvocation", "相关元数据：调用工具");
            writer.setOnlyAlias(true);
            // 一次性写出内容，使用默认样式，强制输出标题
            writer.write(rows, true);
            // 关闭writer，释放内存
            writer.close();

            log.info(" ### AliyunBailianUtils[writeExcel] 写入文件[成功] ==>> {}.", fullPath);
            return true;
        } catch (Exception e) {
            log.error(" ### AliyunBailianUtils[writeExcel] 写入文件失败", e);
            return false;
        }

    }

    // 建议dashscope SDK的版本 >= 2.12.0

    /**
     * 大模型调用 2025/3/19 21:06
     *
     * @param dashscopeApikey 1
     * @param userContent     2
     * @return java.lang.String
     * @author Yaco
     * @since 1.0.0
     */
    public static String call(String dashscopeApikey, String userContent) {
        long start = System.currentTimeMillis();
        log.info(" ### AliyunBailianUtils call start...");
        try {
            GenerationResult generationResult = callWithMessage(dashscopeApikey, userContent);
            log.info(" ### AliyunBailianUtils call GenerationResult: {}", JSONUtil.toJsonPrettyStr(generationResult));
            if (generationResult != null && generationResult.getOutput() != null && CollUtil.isNotEmpty(generationResult.getOutput().getChoices()) && generationResult.getOutput().getChoices().get(0) != null && generationResult.getOutput().getChoices().get(0).getMessage() != null && StrUtil.isNotBlank(generationResult.getOutput().getChoices().get(0).getMessage().getContent())) {
                String content = generationResult.getOutput().getChoices().get(0).getMessage().getContent();
                if ("9999".equals(content)) {
                    throw new Exception("返回结果异常");
                }
                return content;
            } else {
                throw new Exception("返回结果结构异常");
            }
        } catch (Exception e) {
            log.error(" ### AliyunBailianUtilsException: ", e);
        }
        log.info(" ### AliyunBailianUtils call 处理耗时: {}ms", (System.currentTimeMillis() - start));
        return null;
    }

    public static GenerationResult callWithMessage(String dashscopeApikey, String userContent) throws ApiException, NoApiKeyException, InputRequiredException {
        Generation gen = new Generation();
        Message systemMsg = Message.builder().role(Role.SYSTEM.getValue()).content(MODEL_ROLE_CONTENT).build();

        Message userMsg = Message.builder().role(Role.USER.getValue()).content(userContent).build();
        GenerationParam param = GenerationParam.builder()
                // 若没有配置环境变量，请用百炼API Key将下行替换为：.apiKey("sk-xxx")
                .apiKey(dashscopeApikey)
                // 此处以qwen-plus为例，可按需更换模型名称。模型列表：https://help.aliyun.com/zh/model-studio/getting-started/models
                .model(MODEL_TYPE).messages(Arrays.asList(systemMsg, userMsg)).resultFormat(GenerationParam.ResultFormat.MESSAGE).build();
        return gen.call(param);
    }


    /**
     * 获取百炼配置 2025/3/19 15:53
     *
     * @param accessKeyID     1
     * @param accessKeySecret 2
     * @return com.aliyun.teaopenapi.models.Config
     * @author Yaco
     * @since 1.0.0
     */
    public static Config getConfig(String accessKeyID, String accessKeySecret) {
        Config config = new Config()
                // 请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID 和 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
                .setAccessKeyId(accessKeyID)
                .setAccessKeySecret(accessKeySecret)
                // Endpoint 请参考 https://api.aliyun.com/product/bailian
                .setEndpoint("bailian.cn-beijing.aliyuncs.com");
        return config;
    }

    /**
     * 获取调用Client 2025/3/19 16:50
     *
     * @param accessKeyID     1
     * @param accessKeySecret 2
     * @return com.aliyun.bailian20231229.Client
     * @author Yaco
     * @since 1.0.0
     */
    public static Client getClient(String accessKeyID, String accessKeySecret) throws Exception {
        return new Client(getConfig(accessKeyID, accessKeySecret));
    }

    /**
     * 查询文件列表 2025/3/19 15:33
     *
     * @param businessSpaceId 3
     * @param categoryId      4
     * @return void
     * @author Yaco
     * @since 1.0.0
     */
//    public static List<String> listFile(Client client, String businessSpaceId, String categoryId) {
//        log.info(" ### listFile start ...");
//        List<String> fileIds = new ArrayList<>();
//        try {
//            ListFileRequest listFileRequest = new ListFileRequest();
//            listFileRequest.setCategoryId(categoryId);
//            ListFileResponse listFileResponse = client.listFile(businessSpaceId, listFileRequest);
//            log.info(" ### listFileResponse ==>> {}", JSONUtil.toJsonStr(listFileResponse));
//
//            if (listFileResponse != null && listFileResponse.getBody().getData() != null && CollUtil.isNotEmpty(listFileResponse.getBody().getData().getFileList())) {
//                List<ListFileResponseBody.ListFileResponseBodyDataFileList> fileList = listFileResponse.getBody().getData().getFileList();
//                for (ListFileResponseBody.ListFileResponseBodyDataFileList data : fileList) {
//                    if (StrUtil.isNotBlank(data.getFileId())) {
//                        fileIds.add(data.getFileId());
//                    } else {
//                        log.info(" ### listFileResponse ==>> 响应结构不正确");
//                    }
//                }
//            } else {
//                log.info(" ### listFileResponse ==>> 不存在文件");
//            }
//            return fileIds;
//        } catch (Exception e) {
//            log.error(" ### AliyunBailianUtilsException: ", e);
//            return null;
//        }
//    }

    /**
     * 删除文件 2025/3/19 19:12
     *
     * @return boolean
     * @author Yaco
     * @since 1.0.0
     */
    /*public static boolean deleteFile(Client client, String fileId, String businessSpaceId) {
        log.info(" ### deleteFile fileId = [{}] ...", fileId);
        try {
            DeleteFileResponse deleteFileResponse = client.deleteFile(fileId, businessSpaceId);
            log.info(" ### deleteFileResponse ==>> {}", JSONUtil.toJsonStr(deleteFileResponse));
            if (deleteFileResponse != null && deleteFileResponse.getBody() != null && deleteFileResponse.getBody().getSuccess()) {
                log.info(" ### deleteFileResponse ==>> 处理成功");
                return true;
            } else {
                log.info(" ### deleteFileResponse ==>> 处理失败");
            }
        } catch (Exception e) {
            log.error(" ### AliyunBailianUtilsException: ", e);
        }
        return false;
    }*/
    public static void main(String[] args) {
        // accessKey配置
        String accessKeyID = "LTAI5tRdRbr1KAMgCfwM5kPr";
        String accessKeySecret = "7Or6KOTQzrpcf4ePUFXdHdz266UNc0";
        // 业务空间ID
        String businessSpaceId = "llm-xgb2ow5pawrjfh57";
        // 文件类目空间ID
        String categoryId = "cate_a492215f700c4d9da550f87fbe77262b_10907408";
        // 知识库ID
        String kbsIndexId = "7br6pnyz7o";
        // 文件名
        String filePath = "/Users/luyou/Downloads/EOS系统功能说明_1.xlsx";

        syncBailianKbs(accessKeyID, accessKeySecret, categoryId, businessSpaceId, kbsIndexId, filePath);


    }

    public static boolean syncBailianKbs(String accessKeyID, String accessKeySecret, String categoryId, String businessSpaceId, String kbsIndexId, String filePath) {
        try {
            // 获取客户端
            Client client = getClient(accessKeyID, accessKeySecret);

            // 1. 查询文件
            ListFileRequest listFileRequest = new ListFileRequest();
            listFileRequest.setCategoryId(categoryId);
            log.info(" ### listFileRequest ==>> {}", JSONUtil.toJsonStr(listFileRequest));
            ListFileResponse listFileResponse = client.listFile(businessSpaceId, listFileRequest);
            log.info(" ### listFileResponse ==>> {}", JSONUtil.toJsonStr(listFileResponse));

            List<String> fileIds = new ArrayList<>();
            List<ListFileResponseBody.ListFileResponseBodyDataFileList> fileList = listFileResponse.getBody().getData().getFileList();
            for (ListFileResponseBody.ListFileResponseBodyDataFileList data : fileList) {
                if (StrUtil.isNotBlank(data.getFileId())) {
                    fileIds.add(data.getFileId());
                }
            }

            // 2. 删除已有文件
            if (CollUtil.isNotEmpty(fileIds)) {
                for (String fileId : fileIds) {
                    DeleteFileResponse deleteFileResponse = client.deleteFile(fileId, businessSpaceId);
                    log.info(" ### deleteFileResponse ==>> {}", JSONUtil.toJsonStr(deleteFileResponse));
                }
            }

            // 3. 为新文件申请租约
            ApplyFileUploadLeaseRequest applyFileUploadLeaseRequest = new ApplyFileUploadLeaseRequest();
            File newFile = FileUtil.file(filePath);
            applyFileUploadLeaseRequest.setFileName(FileUtil.getName(newFile));
            applyFileUploadLeaseRequest.setMd5(getFileMd5(filePath));
            applyFileUploadLeaseRequest.setSizeInBytes(String.valueOf(FileUtil.size(newFile)));
            log.info(" ### applyFileUploadLeaseRequest ==>> {}", JSONUtil.toJsonStr(applyFileUploadLeaseRequest));
            ApplyFileUploadLeaseResponse applyFileUploadLeaseResponse = client.applyFileUploadLease(categoryId, businessSpaceId, applyFileUploadLeaseRequest);
            log.info(" ### applyFileUploadLeaseResponse ==>> {}", JSONUtil.toJsonStr(applyFileUploadLeaseResponse));
            ApplyFileUploadLeaseResponseBody.ApplyFileUploadLeaseResponseBodyData uploadLeaseData = applyFileUploadLeaseResponse.getBody().getData();
            Object headers = uploadLeaseData.getParam().getHeaders();
            Map<String, String> headersMap = Convert.toMap(String.class, String.class, headers);
            headersMap.forEach((s, s2) -> {
                log.info(" ### headers 键={}, 值={}", s, s2);
            });

            // 4. 上传文件
            boolean uploadFileFlag = UploadFile.uploadFile(uploadLeaseData.getParam().url, filePath, uploadLeaseData.getParam().getMethod(), headersMap.get("X-bailian-extra"), headersMap.get("Content-Type"));
            if(!uploadFileFlag){
                throw new Exception("文档上传失败");
            }

            // 5. 将文档添加至百炼的数据管理
            AddFileRequest addFileRequest = new AddFileRequest();
            addFileRequest.setLeaseId(uploadLeaseData.getFileUploadLeaseId());
            addFileRequest.setCategoryId(categoryId);
            addFileRequest.setParser("DASHSCOPE_DOCMIND");
            log.info(" ### addFileRequest ==>> {}", JSONUtil.toJsonStr(addFileRequest));
            AddFileResponse addFileResponse = client.addFile(businessSpaceId, addFileRequest);
            log.info(" ### addFileResponse ==>> {}", JSONUtil.toJsonStr(addFileResponse));
            // 待添加知识库的文件ID
            List<String> addIds = new ArrayList<>();
            addIds.add(addFileResponse.getBody().getData().getFileId());


            // 5. 查看文档解析状态
            int i = 1;
            int tryCount = 10;
            boolean isTry = true;
            // 是否已解析完成
            boolean isSuccessParse = false;
            while (isTry && i <= tryCount) {
                log.info(" ### describeFileResponse ==>> 开始调用describeFile...");
                DescribeFileResponse describeFileResponse = client.describeFile(businessSpaceId, addFileResponse.getBody().getData().getFileId());
                log.info(" ### describeFileResponse ==>> {}", JSONUtil.toJsonStr(describeFileResponse));
                String successStatus = describeFileResponse.getBody().getData().getStatus();
                if ("PARSE_SUCCESS".equals(successStatus)) {
                    isSuccessParse = true;
                    isTry = false;
                    log.info(" ### describeFileResponse ==>> 已经解析完成");
                } else {
                    ThreadUtil.safeSleep(20 * 1000L);
                    log.info(" ### describeFileResponse ==>> 未解析完成");
                }
                i++;
            }
            if (!isSuccessParse) {
                throw new Exception("文档解析状态[未完成]");
            }


            // 6. 查询知识库下文档
            ListIndexDocumentsRequest listIndexDocumentsRequest = new ListIndexDocumentsRequest();
            listIndexDocumentsRequest.setIndexId(kbsIndexId);
            log.info(" ### listIndexDocumentsRequest ==>> {}", JSONUtil.toJsonStr(listIndexDocumentsRequest));
            ListIndexDocumentsResponse listIndexDocumentsResponse = client.listIndexDocuments(businessSpaceId, listIndexDocumentsRequest);
            log.info(" ### listIndexDocumentsResponse ==>> {}", JSONUtil.toJsonStr(listIndexDocumentsResponse));
            // 待删除的知识库文档
            List<String> removeIds = new ArrayList<>();
            List<ListIndexDocumentsResponseBody.ListIndexDocumentsResponseBodyDataDocuments> documents = listIndexDocumentsResponse.getBody().getData().getDocuments();
            for (ListIndexDocumentsResponseBody.ListIndexDocumentsResponseBodyDataDocuments document : documents) {
                removeIds.add(document.getId());
            }

            // 7. 知识库中追加文档
            SubmitIndexAddDocumentsJobRequest submitIndexAddDocumentsJobRequest = new SubmitIndexAddDocumentsJobRequest();
            submitIndexAddDocumentsJobRequest.setIndexId(kbsIndexId);
            submitIndexAddDocumentsJobRequest.setSourceType("DATA_CENTER_FILE");
            submitIndexAddDocumentsJobRequest.setDocumentIds(addIds);
            log.info(" ### submitIndexAddDocumentsJobRequest ==>> {}", JSONUtil.toJsonStr(submitIndexAddDocumentsJobRequest));
            SubmitIndexAddDocumentsJobResponse submitIndexAddDocumentsJobResponse = client.submitIndexAddDocumentsJob(businessSpaceId, submitIndexAddDocumentsJobRequest);
            log.info(" ### submitIndexAddDocumentsJobResponse ==>> {}", JSONUtil.toJsonStr(submitIndexAddDocumentsJobResponse));
            String jobID = submitIndexAddDocumentsJobResponse.getBody().getData().getId();

            // 8. 查询知识库追加文档状态, GetIndexJobStatus接口（追加任务完成后，该请求返回中的Data.Status值为COMPLETED）。
            int n = 1;
            int retryCount = 10;
            boolean isRetry = true;
            // 是否已解析完成
            boolean isSuccessGetIndexJobStatus = false;
            while (isRetry && n <= retryCount) {
                GetIndexJobStatusRequest getIndexJobStatusRequest = new GetIndexJobStatusRequest();
                getIndexJobStatusRequest.setIndexId(kbsIndexId);
                getIndexJobStatusRequest.setJobId(jobID);
                log.info(" ### getIndexJobStatusRequest ==>> {}", JSONUtil.toJsonStr(getIndexJobStatusRequest));
                GetIndexJobStatusResponse getIndexJobStatusResponse = client.getIndexJobStatus(businessSpaceId, getIndexJobStatusRequest);
                log.info(" ### getIndexJobStatusResponse ==>> {}", JSONUtil.toJsonStr(getIndexJobStatusResponse));
                String getIndexJobStatus = getIndexJobStatusResponse.getBody().getData().getStatus();
                if ("COMPLETED".equals(getIndexJobStatus)) {
                    isSuccessGetIndexJobStatus = true;
                    isRetry = false;
                    log.info(" ### getIndexJobStatusResponse ==>> 已经解析完成");
                } else {
                    ThreadUtil.safeSleep(20 * 1000L);
                    log.info(" ### getIndexJobStatusResponse ==>> 未解析完成");
                }
                n++;
            }
            if (!isSuccessGetIndexJobStatus) {
                throw new Exception("知识库中追加文档状态[未完成]");
            }

            // 9. 从知识库中移除指定文档, 调用DeleteIndexDocument接口。
            DeleteIndexDocumentRequest deleteIndexDocumentRequest = new DeleteIndexDocumentRequest();
            deleteIndexDocumentRequest.setIndexId(kbsIndexId);
            deleteIndexDocumentRequest.setDocumentIds(removeIds);
            log.info(" ### deleteIndexDocumentRequest ==>> {}", JSONUtil.toJsonStr(deleteIndexDocumentRequest));
            DeleteIndexDocumentResponse deleteIndexDocumentResponse = client.deleteIndexDocument(businessSpaceId, deleteIndexDocumentRequest);
            log.info(" ### deleteIndexDocumentResponse ==>> {}", JSONUtil.toJsonStr(deleteIndexDocumentResponse));
            return true;
        } catch (Exception e) {
            log.error(" ### AliyunBailianUtils[syncBailianKbs] Exception: ", e);
        }
        return false;
    }


}
